/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */
package com.googlecode.ivybeans.module;

import com.googlecode.ivybeans.facade.IvyLibrary;
import com.googlecode.ivybeans.facade.IvyLibraryException;
import com.googlecode.ivybeans.facade.ResolvedIvyLibrary;
import com.googlecode.ivybeans.module.classpath.ClassPathScope;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.netbeans.api.project.Project;
import org.openide.ErrorManager;
import org.openide.filesystems.FileChangeAdapter;
import org.openide.filesystems.FileEvent;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileRenameEvent;
import org.openide.filesystems.FileUtil;
import org.openide.util.NbBundle;
import org.openide.util.RequestProcessor;

/**
 *
 * @author lforet
 */
public class IvyLibraryController {

    private final Project project;
    private boolean ivyEnableState = false;
    private final IvyLibrary ivyLibrary = new IvyLibrary();
    private final IvyResolver ivyResolver = new IvyResolver();
    private final IvyExtender ivyExtender = new IvyExtender();
    private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
    private final DescriptorFileListener descriptorFileListener = new DescriptorFileListener();
    private final SettingsFileListener settingsFileListener = new SettingsFileListener();
    private final ConfigurationMapping configurationMapping;
    public final static String[] DEFAULT_CONF = new String[]{"*"};
    public final static String SETTINGS_CHANGE = "ivySettingsChange";
    public final static String DESCRIPTOR_CHANGE = "ivyDescriptorChange";
    public final static String IVY_ENABLE_STATE_CHANGE = "ivyEnableStateChange";
    public final static String CONFIGURATION_CHANGE = "ivyConfigurationChange";
    public final static String IVY_LIBRARY_RESOLVED = "ivyLibraryResolved";

    public IvyLibraryController(Project project, boolean isEnabled, File descriptorFile, File settingsFile, ConfigurationMapping configurationMapping) {
        assert project != null;
        this.project = project;
        addPropertyChangeListener(ivyResolver);
        setEnableState(isEnabled);
        setDescriptorFile(descriptorFile);
        assert configurationMapping != null;
        this.configurationMapping = configurationMapping;
        setSettingsFile(settingsFile);
        addPropertyChangeListener(ivyExtender);
    }

//    public Project getProject() {
//        return project;
//    }
    public void setEnableState(boolean enableState) {
        boolean oldIvyEnableState = ivyEnableState;
        ivyEnableState = enableState;
        if (oldIvyEnableState != ivyEnableState) {
            pcs.firePropertyChange(IVY_ENABLE_STATE_CHANGE, oldIvyEnableState, ivyEnableState);
        }
    }

    public boolean isIvyEnabled() {
        return ivyEnableState;
    }

    public void setDescriptorFile(File file) {
        try {
            File oldFile = getDescriptorFileFromLibrary();

            if (oldFile != null) {
                FileObject oldDescriptorFO = FileUtil.toFileObject(oldFile);
                if (oldDescriptorFO != null) {
                    oldDescriptorFO.removeFileChangeListener(descriptorFileListener);
                }
            }
            URL descriptorURL = null;
            if (file != null && file.isFile()) {
                FileObject descriptorFO = FileUtil.toFileObject(file);
                if (descriptorFO != null) {
                    descriptorFO.addFileChangeListener(descriptorFileListener);
                }
                descriptorURL = file.toURL();
            }

            ivyLibrary.setDescriptorURL(descriptorURL);
            pcs.firePropertyChange(DESCRIPTOR_CHANGE, oldFile, file);
        } catch (MalformedURLException ex) {
            ErrorManager.getDefault().notify(ex);
        }
    }

    public File getDescriptorFile() {
        return getDescriptorFileFromLibrary();
    }

    public FileObject getDescriptorFO() {
        File file = getDescriptorFileFromLibrary();
        return (file != null)
                ? FileUtil.toFileObject(file)
                : null;
    }

    public void setSettingsFile(File file) {
        try {
            File oldFile = getSettingsFileFromLibrary();
            if (oldFile != null) {
                FileObject oldSettingsFO = FileUtil.toFileObject(oldFile);
                if (oldSettingsFO != null) {
                    oldSettingsFO.removeFileChangeListener(settingsFileListener);
                }
            }
            URL settingsURL = null;
            if (file != null && file.isFile()) {
                FileObject settingsFO = FileUtil.toFileObject(file);
                if (settingsFO != null) {
                    settingsFO.addFileChangeListener(settingsFileListener);
                }
                settingsURL = file.toURL();
            }
            ivyLibrary.setSettingsURL(settingsURL);
            pcs.firePropertyChange(SETTINGS_CHANGE, oldFile, file);
        } catch (MalformedURLException ex) {
            ErrorManager.getDefault().notify(ex);
        }
    }

    public File getSettingsFile() {
        return getSettingsFileFromLibrary();
    }

    public void triggerIvyResolution() {
        ivyResolver.triggerResolution();
    }

    public void addPropertyChangeListener(PropertyChangeListener propertyChangeListener) {
        pcs.addPropertyChangeListener(propertyChangeListener);
    }

    public void removePropertyChangeListener(PropertyChangeListener propertyChangeListener) {
        pcs.removePropertyChangeListener(propertyChangeListener);
    }

    private File getDescriptorFileFromLibrary() {
        if (ivyLibrary.getDescriptorURL() != null) {
            try {
                return new File(ivyLibrary.getDescriptorURL().toURI());
            } catch (URISyntaxException ex) {
                ErrorManager.getDefault().notify(ex);
            }
        }
        return null;
    }

    private File getSettingsFileFromLibrary() {
        if (ivyLibrary.getDescriptorURL() != null) {
            try {
                return "jar".equalsIgnoreCase(ivyLibrary.getSettingsURL().getProtocol())
                        ? null
                        : new File(ivyLibrary.getSettingsURL().toURI());
            } catch (URISyntaxException ex) {
                ErrorManager.getDefault().notify(ex);
            }
        }
        return null;
    }

    public IvyLibrary getIvyLibrary() {
        return ivyLibrary;
    }

    public ResolvedIvyLibrary getResolvedLibrary(ClassPathScope scope) {
        return configurationMapping.getResolvedIvyLibrary(scope);
    }

    public ConfigurationMapping getConfigurationMapping() {
        return configurationMapping;
    }

    public void setConfiguration(ClassPathScope scope, String[] confs) {
        configurationMapping.put(scope, confs, ResolvedIvyLibrary.EMPTY_RESOLVED);
        pcs.firePropertyChange(CONFIGURATION_CHANGE, null, null);
    }

    private class IvyResolver implements PropertyChangeListener {

        private RequestProcessor.Task ivyResolverTask;

        public IvyResolver() {

            ivyResolverTask = RequestProcessor.getDefault().create(new Runnable() {

                public void run() {
                    ProgressHandle progressHandle = ProgressHandleFactory.createHandle(NbBundle.getMessage(IvyLibraryController.class, "LBL_Resolving_Progress"));
                    progressHandle.start();
                    try {
                        for (ClassPathScope scope : ClassPathScope.values()) {
                            String[] confs = configurationMapping.getConfiguration(scope);
                            ResolvedIvyLibrary resolvedIvyLibrary = ivyLibrary.resolve(confs);
                            configurationMapping.put(scope, confs, resolvedIvyLibrary);
                        }
                        pcs.firePropertyChange(IVY_LIBRARY_RESOLVED, null, null);
                    } catch (IvyLibraryException ex) {
                        ErrorManager.getDefault().notify(ex);
                    } finally {
                        progressHandle.finish();
                    }
                }
            });
        }

        public void propertyChange(PropertyChangeEvent evt) {
            if (DESCRIPTOR_CHANGE.equals(evt.getPropertyName()) || SETTINGS_CHANGE.equals(evt.getPropertyName()) || CONFIGURATION_CHANGE.equals(evt.getPropertyName())) {
                triggerResolution();
            }
        }

        public void triggerResolution() {
            ivyResolverTask.schedule(2000);
        }
    }

    private class IvyExtender implements PropertyChangeListener {

        private RequestProcessor.Task extendTask;

        public IvyExtender() {

            extendTask = RequestProcessor.getDefault().create(new Runnable() {

                public void run() {
//
//                    try {
//                        ProjectManager.mutex().writeAccess(new Mutex.ExceptionAction<Void>() {
//
//                            public Void run() throws Exception {
                    if (isIvyEnabled()) {
                        IvyConfigurationHelper.getInstance().store(project);
                        IvyBuildHelper.getInstance().createIvyImpl(project, getDescriptorFile(), getSettingsFile());
                    } else {
                        IvyConfigurationHelper.getInstance().clean(project);
                        IvyBuildHelper.getInstance().clean(project);

                    }
//                                return null;
//                            }
//                        });
//                        ProjectManager.getDefault().saveProject(project);
//                    } catch (IOException ex) {
//                        ErrorManager.getDefault().notify(ex);
//                    } catch (IllegalArgumentException ex) {
//                        ErrorManager.getDefault().notify(ex);
//                    } catch (MutexException ex) {
//                        ErrorManager.getDefault().notify(ex.getException());
//                    }
                }
            });

        }

        public void propertyChange(PropertyChangeEvent evt) {
            if (DESCRIPTOR_CHANGE.equals(evt.getPropertyName()) 
                    || SETTINGS_CHANGE.equals(evt.getPropertyName()) 
                    || IVY_ENABLE_STATE_CHANGE.equals(evt.getPropertyName())
                    || CONFIGURATION_CHANGE.equals(evt.getPropertyName())
                    ) {
                triggerExtend();
            }
        }

        private void triggerExtend() {
            extendTask.schedule(200);
        }
    }

    private class DescriptorFileListener extends FileChangeAdapter {

        @Override
        public void fileChanged(FileEvent fe) {
            triggerIvyResolution();
        }

        @Override
        public void fileDeleted(FileEvent fe) {
            setDescriptorFile(null);
        }

        @Override
        public void fileRenamed(FileRenameEvent fe) {
            setDescriptorFile(FileUtil.toFile(fe.getFile()));
        }
    }

    private class SettingsFileListener extends FileChangeAdapter {

        @Override
        public void fileChanged(FileEvent fe) {
            triggerIvyResolution();
        }

        @Override
        public void fileDeleted(FileEvent fe) {
            setSettingsFile(null);
        }

        @Override
        public void fileRenamed(FileRenameEvent fe) {
            setSettingsFile(FileUtil.toFile(fe.getFile()));
        }
    }

    public static class ConfigurationMapping {

        private Map<ClassPathScope, String[]> configurationMap = new HashMap<ClassPathScope, String[]>();
        private Map<ClassPathScope, ResolvedIvyLibrary> resolvedIvyLibraryMap = new HashMap<ClassPathScope, ResolvedIvyLibrary>();

        public ConfigurationMapping() {
            for (ClassPathScope scope : ClassPathScope.values()) {
                put(scope, DEFAULT_CONF, ResolvedIvyLibrary.EMPTY_RESOLVED);
            }
        }

        public void put(ClassPathScope scope, String[] confs, ResolvedIvyLibrary resolvedIvyLibrary) {
            synchronized (configurationMap) {
                resolvedIvyLibraryMap.put(scope, resolvedIvyLibrary);
                configurationMap.put(scope, confs);
            }
        }

        public String[] getConfiguration(ClassPathScope scope) {
            return configurationMap.get(scope);
        }

        public ResolvedIvyLibrary getResolvedIvyLibrary(ClassPathScope scope) {
            return resolvedIvyLibraryMap.get(scope);
        }
    }
}
